package xyz.scootaloo.kami.server.verticle

import io.vertx.config.ConfigRetriever
import io.vertx.core.json.JsonObject
import io.vertx.kotlin.config.configRetrieverOptionsOf
import io.vertx.kotlin.config.configStoreOptionsOf
import io.vertx.kotlin.core.json.jsonObjectOf
import io.vertx.kotlin.coroutines.await
import xyz.scootaloo.kami.server.standard.getLogger
import xyz.scootaloo.kami.server.standard.safeGetInteger
import xyz.scootaloo.kami.server.standard.safeGetString
import xyz.scootaloo.kami.server.service.*

/**
 * 配置加载器
 *
 * @author flutterdash@qq.com
 * @since 2022/3/18 13:37
 */
object ConfigLoader {
    private const val customizeConfigPath = "./conf/config.json"

    private val log by lazy { getLogger() }
    private val fs get() = vertx.fileSystem()

    /**
     * 调用后, 会从两个位置读取配置
     *
     * 1. 默认从classpath下的config.json读默认配置
     * 2. 从conf/config.json读取用户配置
     *
     * 用户配置会覆盖默认配置, 如果某项配置用户的格式错误, 则不会覆盖
     */
    suspend fun loadConfig(): AppConfig {
        val config = AppConfig()
        loadAndMergeConfig("config.json", config)
        loadAndMergeConfig(customizeConfigPath, config)
        log.info("配置加载完成")
        return config
    }

    private suspend fun loadAndMergeConfig(path: String, config: AppConfig) {
        val exists = fs.exists(path).await()
        if (!exists) {
            log.warn("配置文件`${path}`不存在")
            return
        }

        val json = loadJsonConfig(path)
        mergeConfig(config, json)
    }

    private suspend fun loadJsonConfig(configPath: String): JsonObject {
        return ConfigRetriever.create(vertx, configRetrieverOptions(configPath)).config.await()
    }

    private fun mergeConfig(config: AppConfig, json: JsonObject) {
        config.serverConfig = safeReadServerConfig(json.getOrEmpty("server"), config.serverConfig)
        config.fileConfig = safeReadFileConfig(json.getOrEmpty("file"), config.fileConfig)
        config.databaseConfig = safeReadDbConfig(json.getOrEmpty("db"), config.databaseConfig)
    }

    private fun safeReadServerConfig(json: JsonObject, refer: ServerConfig): ServerConfig {
        val defConfig = ServerConfig()
        defConfig.port = json.safeGetInteger(Constant.PORT, refer.port)
        return defConfig
    }

    private fun safeReadDbConfig(json: JsonObject, refer: DatabaseConfig): DatabaseConfig {
        val defConfig = DatabaseConfig()
        defConfig.path = json.getString(Constant.PATH, refer.path)
        return defConfig
    }

    private fun safeReadFileConfig(json: JsonObject, refer: FileConfig): FileConfig {
        val defConfig = FileConfig()
        defConfig.homePath = json.safeGetString(Constant.HOME_KEY, refer.homePath)
        defConfig.assetPath = json.safeGetString(Constant.ASSETS_KEY, refer.assetPath)
        defConfig.cachePath = json.safeGetString(Constant.CACHE_KEY, refer.cachePath)
        return defConfig
    }

    private fun JsonObject.getOrEmpty(key: String): JsonObject = getJsonObject(key) ?: jsonObjectOf()

    private fun configRetrieverOptions(configPath: String) = configRetrieverOptionsOf()
        .addStore(configStoreOptions(configPath))

    private fun configStoreOptions(configPath: String) = configStoreOptionsOf(
        type = "file", format = "json", config = jsonObjectOf(
            "path" to configPath
        )
    )
}